/**
* Copyright (C) 2008 by The Regents of the University of California
* Redistribution of this file is permitted under the terms of the GNU
* Public License (GPL).
*
* @author Junghoo "John" Cho <cho AT cs.ucla.edu>
* @date 3/24/2008
*/

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
	fprintf(stdout, "Bruinbase> ");

	// set the command line input and start parsing user input
	sqlin = commandline;
	sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
	// SqlParser.y by bison (bison is GNU equivalent of yacc)

	return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
	RecordFile rf;   // RecordFile containing the table
	RecordId   rid;  // record cursor for table scanning

	RC     rc;
	int    key;     
	string value;
	int    count;
	int    diff;
	BTreeIndex Btree;
	bool isIndxd = false;
	bool isKeyCmp = false;
	
	int bottom = 0;
	int top = 0x7fffffff;

	// open the table file
	if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
		fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
		return rc;
	}
	
	//Check if tree exists
	if ( (rc = Btree.open(table + ".idx", 'r')) == 0 )
	{
		isIndxd = true;
	}

	//Check if the select relies on the key attribute
	//This is when the tree can help with the query
	
	for ( int it = 0; it < cond.size(); it++ )
	{
		if ( cond[it].attr == 1 )
		{
		isKeyCmp = true;
		int cmpVal = atoi(cond[it].value);
		
		switch( cond[it].comp )
		{
		case SelCond::NE:
			break;
			
		case SelCond::EQ:
			if ( cmpVal > bottom )
			{
				bottom = cmpVal;
			}
			if ( cmpVal < top )
			{
				top = cmpVal;
			}
			break;
			
		case SelCond::GT:
			if ( (cmpVal+1) > bottom )
			{
				bottom = cmpVal+1;
			}
			break;
			
		case SelCond::LT:
			if ( (cmpVal - 1) < top )
			{
				top = cmpVal-1;
			}
			break;
			
		case SelCond::GE:
			if ( cmpVal > bottom )
			{
				bottom = cmpVal;
			}
			break;
		
		case SelCond::LE:
			if ( cmpVal < top )
			{
				top = cmpVal;
			}
			break;
			
		default:
			return RC_INVALID_ATTRIBUTE;
		}
		}
	}

	//If theres an index that can be used for the select
	//Nonlinear approach
	if ( isIndxd && isKeyCmp )
	{
		RecordId rid;
		IndexCursor curs;
		count = 0;
		
		Btree.locate(bottom, curs);
		
		while ( top >= bottom && Btree.readForward(curs, key, rid) != RC_END_OF_TREE &&
		         key <= top )
		{
			if ((rc = rf.read(rid, key, value)) < 0) {
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				goto exit_select;
			}
			count++;
			
			// print the tuple 
			switch (attr) {
			case 1:  // SELECT key
				fprintf(stdout, "%d\n", key);
				break;
			case 2:  // SELECT value
				fprintf(stdout, "%s\n", value.c_str());
				break;
			case 3:  // SELECT *
				fprintf(stdout, "%d '%s'\n", key, value.c_str());
				break;
			}
		}
		
		
		// print matching tuple count if "select count(*)"
		if (attr == 4) {
		fprintf(stdout, "%d\n", count);
		}
	}
	
	
	//If there is no index or it can't be used for the select
	//Normal approach
	else
	{
		// scan the table file from the beginning
		rid.pid = rid.sid = 0;
		count = 0;
		while (rid < rf.endRid()) {
			// read the tuple
			if ((rc = rf.read(rid, key, value)) < 0) {
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				goto exit_select;
			}

			// check the conditions on the tuple
			for (unsigned i = 0; i < cond.size(); i++) {
				// compute the difference between the tuple value and the condition value
				switch (cond[i].attr) {
				case 1:
					diff = key - atoi(cond[i].value);
					break;
				case 2:
					diff = strcmp(value.c_str(), cond[i].value);
					break;
				}

				// skip the tuple if any condition is not met
				switch (cond[i].comp) {
				case SelCond::EQ:
					if (diff != 0) goto next_tuple;
					break;
				case SelCond::NE:
					if (diff == 0) goto next_tuple;
					break;
				case SelCond::GT:
					if (diff <= 0) goto next_tuple;
					break;
				case SelCond::LT:
					if (diff >= 0) goto next_tuple;
					break;
				case SelCond::GE:
					if (diff < 0) goto next_tuple;
					break;
				case SelCond::LE:
					if (diff > 0) goto next_tuple;
					break;
				}
			}

			// the condition is met for the tuple. 
			// increase matching tuple counter
			count++;

			// print the tuple 
			switch (attr) {
			case 1:  // SELECT key
				fprintf(stdout, "%d\n", key);
				break;
			case 2:  // SELECT value
				fprintf(stdout, "%s\n", value.c_str());
				break;
			case 3:  // SELECT *
				fprintf(stdout, "%d '%s'\n", key, value.c_str());
				break;
			}

			// move to the next tuple
			next_tuple:
			++rid;
		}

		// print matching tuple count if "select count(*)"
		if (attr == 4) {
			fprintf(stdout, "%d\n", count);
		}
		rc = 0;
	}

	// close the table file and return
	exit_select:
	rf.close();
	if ( isIndxd )
	{
		Btree.close();
	}
	return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	//Attempt to open the loadfile
	ifstream infile( loadfile.c_str(), ifstream::in );
	if ( !infile.is_open() )
	{
		//Could not open the load file.
		return RC_FILE_OPEN_FAILED;
	}

	//Open the RecordFile
	//Return any errors while opening
	const string RFName = table + ".tbl";
	RC rc;
	RecordFile RecFile;
	if ( (rc = RecFile.open( RFName, 'w')) < 0)
	{
		return rc;
	}


	int key;
	string value;
	string line;
	RecordId tmprid;
	BTreeIndex Btree;
	if ( index )
	{
		Btree.open(table+".idx", 'w');
	}

	//Each item needs to be loaded into a record and index if index
	while ( !infile.eof() )
	{
		//Read in each line
		getline(infile, line);
		if ( infile.fail() || infile.bad() )
		{
			return RC_FILE_READ_FAILED;
		}
		
		//Parse the line for the correct key and value
		if ( (rc = parseLoadLine( line, key, value )) < 0 )
		{
			return rc;
		}
		
		//Put the key and value into RecFile
		if ( (rc = RecFile.append( key, value, tmprid )) < 0 )
		{
			return rc;  
		}
		
		//If there is a tree needed: insert the right key and rid into it
		if ( index )
		{
			Btree.insert(key, tmprid);
		}
	}

	//Close record file
	if ( rc = RecFile.close() )
	{
		return rc;
	}

	//Close the index tree
	if ( index )
	{
		Btree.close();
	}

	//Close the input file
	infile.close();

	return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
	const char *s;
	char        c;
	string::size_type loc;
	
	// ignore beginning white spaces
	c = *(s = line.c_str());
	while (c == ' ' || c == '\t') { c = *++s; }

	// get the integer key value
	key = atoi(s);

	// look for comma
	s = strchr(s, ',');
	if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

	// ignore white spaces
	do { c = *++s; } while (c == ' ' || c == '\t');
	
	// if there is nothing left, set the value to empty string
	if (c == 0) { 
		value.erase();
		return 0;
	}

	// is the value field delimited by ' or "?
	if (c == '\'' || c == '"') {
		s++;
	} else {
		c = '\n';
	}

	// get the value string
	value.assign(s);
	loc = value.find(c, 0);
	if (loc != string::npos) { value.erase(loc); }

	return 0;
}
